#include <stdio.h>

#include "test_backtracer_arm_exidx.h"
#include "../src/backtracer_arm_exidx.h"
#include "../src/errors.h"
#include "../src/log.h"

//------------------------------------------------------------------------------

#define STACK_SIZE 1024 
unwind_status status;
unwind_status status_old;
addr_t stack[STACK_SIZE];

TBGError ret;
#define BUF_SIZE 16
unsigned int buf[BUF_SIZE];

//------------------------------------------------------------------------------

void SetupUnchecked() {
    InitLog(stdout);
    ret = 0;
    int i = 0;
    for(i = 0; i < STACK_SIZE; ++i)
        stack[i] = 0;

    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        status.regs[i] = status_old.regs[i] = i;

    status.r15_updated = false;
    status_old.r15_updated = false;
    status.regs[13] = status_old.regs[13] = (addr_t) stack + 
                                            (STACK_SIZE - 1) * sizeof(addr_t);
}

//------------------------------------------------------------------------------

void SetupChecked() {
    int i = 0;
    for(i = 0; i < BUF_SIZE; ++i)
        buf[i] = 0;
    ret = 0;
    status.regs[13] = status_old.regs[13] = (addr_t) stack + 
                                            (STACK_SIZE - 1) * sizeof(addr_t);
    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        status.regs[i] = status_old.regs[i];
    status.r15_updated = status_old.r15_updated;
}

//------------------------------------------------------------------------------

void TearDown() {
    //nothing to do yet
}

//------------------------------------------------------------------------------

START_TEST (test_DataPushPop) {
    // 00xxxxxx + 01xxxxxx
    buf[0] = 0x40; // simple data push
    ret = ArmExidxDecode(&status, buf, 1);
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[13] == status_old.regs[13] - 4, "Invalid DATA_PUSH");

    buf[1] = 0x01; // simple data pop. 
    ret = ArmExidxDecode(&status, buf, 2); //push and double pop of size 4
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[13] == status_old.regs[13], "Invalid DATA_PUSH");

    buf[2] = 0x46; //PUSH
    buf[3] = 0x67; //PUSH
    buf[4] = 0x2D; //POP previous double push
    ret = ArmExidxDecode(&status, buf, 5); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[13] == status_old.regs[13], "Invalid DATA_PUSH");

    int i = 0;
    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        fail_unless(status_old.regs[i] == status.regs[i], "Registers corrupted");

    // 10110010 uleb128
    // TODO: test this case

}
END_TEST

//------------------------------------------------------------------------------

START_TEST (test_RefuseReserved) {
    int i = 0;
    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        fail_unless(status_old.regs[i] == status.regs[i], "Registers corrupted");
    
    buf[0] = 0x80; //refuse
    buf[1] = 0;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == REFUSE_UNW_COMMAND, "ArmExidxDecode failed with [%d]=[%s]"
                "but should failed with [%d]=[%s]", ret, StrError(ret),
                REFUSE_UNW_COMMAND, StrError(REFUSE_UNW_COMMAND));
    //refuse
    buf[0] = 0x41; // simple data push
    buf[1] = 0x01; // simple data pop. 
    buf[2] = 0x80;
    buf[3] = 0;
    ret = ArmExidxDecode(&status, buf, 4); 
    fail_unless(ret == REFUSE_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(REFUSE_UNW_COMMAND));
    //reserved
    buf[0] = 0x9D;
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));
    //reserved
    buf[0] = 0x9F;
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));

    //reserved
    buf[0] = 0xB1;
    buf[1] = 0x9a;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));
    //reserved
    buf[0] = 0xB6;
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));
    //reserved
    buf[0] = 0xC7;
    buf[1] = 0x00;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));
    //reserved
    buf[0] = 0xC7;
    buf[1] = 0x15;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == RESERVED_UNW_COMMAND, "ArmExidxDecode failed with [%s]"
                "but should failed with [%s]", StrError(ret),
                StrError(RESERVED_UNW_COMMAND));

//    int i = 0;
    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        fail_unless(status_old.regs[i] == status.regs[i], "Registers corrupted");
    
}
END_TEST

//------------------------------------------------------------------------------

START_TEST (test_RegPop) {
    // 1000iiii iiiiiiii
    buf[0] = 0x8f; //pop all
    buf[1] = 0xff;
    int i = 0;
    for(i = 4; i < 16; ++i) 
        stack[STACK_SIZE - 1 - (16 - i)] = 100 * i;
    status.regs[13] = status_old.regs[13];
    status.regs[13] -= 12 * sizeof(addr_t);

    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 4; ++i) 
        fail_unless(status.regs[i] == status_old.regs[i], "Registes corrupted");
    for(i = 4; i < 16; ++i) {
        fail_unless(status.regs[i] == 100 * i, "Invalid registers pop");
        status.regs[i] = status_old.regs[i];
    }
    fail_unless(status.r15_updated, "Flag for r15 update should be set");
    
    //ufff
    stack[STACK_SIZE - 2] = 0xff;
    status.regs[13] = status_old.regs[13] - sizeof(addr_t);
    buf[0] = 0x80; //pop only r11;
    buf[1] = 0x80;
    ret = ArmExidxDecode(&status, buf, 2); 

    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 11)
            fail_unless(status.regs[i] == status_old.regs[i], "Registes corrupted");
    fail_unless(!status.r15_updated, "Flag for r15 update should be reseted");
    fail_unless(status.regs[11] == 0xff, "Invalid r11 after pop");
    status.regs[11] = status_old.regs[11];

    stack[STACK_SIZE - 2] = 0xbb;
    status.regs[13] = status_old.regs[13] - sizeof(addr_t);
    buf[0] = 0x80; //pop only r8;
    buf[1] = 0x10;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 8)
            fail_unless(status.regs[i] == status_old.regs[i], "Registes corrupted");
    fail_unless(!status.r15_updated, "Flag for r5 update should be reseted");
    fail_unless(status.regs[8] == 0xbb, "Invalid r8 after pop");
    status.regs[8] = status_old.regs[8];

    stack[STACK_SIZE - 2] = 0xcc;
    status.regs[13] = status_old.regs[13] - sizeof(addr_t);
    buf[0] = 0x82; //pop only r13;
    buf[1] = 0x00;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 13)
            fail_unless(status.regs[i] == status_old.regs[i], "Registes corrupted");
    fail_unless(status.regs[13] == 0xcc, "Invalid r13 after pop");
    status.regs[13] = status_old.regs[13];

    stack[STACK_SIZE - 2] = 0x15;
    stack[STACK_SIZE - 3] = 0x12;
    stack[STACK_SIZE - 4] = 0x11;
    stack[STACK_SIZE - 5] = 0x7;
    stack[STACK_SIZE - 6] = 0x4;
    status.regs[13] = status_old.regs[13] - 5 * sizeof(addr_t);
    buf[0] = 0x89; //pop only r15, r12, r11, r7, r4;
    buf[1] = 0x89;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 4; i < 16; ++i) 
        if(i != 15 && i != 13 && i != 12 && i != 11 && i != 7 && i != 4)
            fail_unless(status.regs[i] == status_old.regs[i], "Registers corrupted");
    fail_unless(status.regs[15] == 0x15, "Invalid r15 after pop");
    fail_unless(status.regs[13] == status_old.regs[13], "Invalid SP after pop");
    fail_unless(status.regs[12] == 0x12, "Invalid r12 after pop");
    fail_unless(status.regs[11] == 0x11, "Invalid r11 after pop");
    fail_unless(status.regs[ 7] == 0x07, "Invalid r07 after pop");
    fail_unless(status.regs[ 4] == 0x04, "Invalid r04 after pop");
    fail_unless(status.r15_updated, "Flag for r15 update should be set");
    for(i = 0; i < 16; ++i) 
        status.regs[i] = status_old.regs[i];
    
    // 10100nnn
    stack[STACK_SIZE - 2] = 0x7c;
    stack[STACK_SIZE - 3] = 0x6c;
    stack[STACK_SIZE - 4] = 0x5c;
    stack[STACK_SIZE - 5] = 0x4c;
    status.regs[13] = status_old.regs[13] - 4 * sizeof(addr_t);
    buf[0] = 0xa3; //pop r4 - r7   
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 4 && i != 5 && i != 6 && i != 7)
            fail_unless(status.regs[i] == status_old.regs[i], "r[%d] corrupted", i);
    fail_unless(status.regs[4] == 0x4c, "Invalid r4 after pop");
    fail_unless(status.regs[5] == 0x5c, "Invalid r5 after pop");
    fail_unless(status.regs[6] == 0x6c, "Invalid r6 after pop");
    fail_unless(status.regs[7] == 0x7c, "Invalid r7 after pop");
    for(i = 4; i <= 7; ++i)
        status.regs[i] = status_old.regs[i];

    stack[STACK_SIZE - 2] = 0xbc;
    status.regs[13] = status_old.regs[13] - sizeof(addr_t);
    buf[0] = 0xa0; //pop r4   
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 4)
            fail_unless(status.regs[i] == status_old.regs[i], "r[%d] corrupted", i);
    fail_unless(status.regs[4] == 0xbc, "Invalid r4 after pop");
    status.regs[4] = status_old.regs[4];

    // 10101nnn
    stack[STACK_SIZE - 2] = 0xdc;
    stack[STACK_SIZE - 3] = 0x8c;
    stack[STACK_SIZE - 4] = 0x7c;
    stack[STACK_SIZE - 5] = 0x6c;
    stack[STACK_SIZE - 6] = 0x5c;
    stack[STACK_SIZE - 7] = 0x4c;
    status.regs[13] = status_old.regs[13] - 6 * sizeof(addr_t);
    buf[0] = 0xac; //pop r4 - r8, r14   
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 4 && i != 5 && i != 6 && i != 7 && i != 8 && i != 14)
            fail_unless(status.regs[i] == status_old.regs[i], "r[%d] corrupted", i);
    fail_unless(status.regs[4] == 0x4c, "Invalid r4 after pop");
    fail_unless(status.regs[5] == 0x5c, "Invalid r5 after pop");
    fail_unless(status.regs[6] == 0x6c, "Invalid r6 after pop");
    fail_unless(status.regs[7] == 0x7c, "Invalid r7 after pop");
    fail_unless(status.regs[8] == 0x8c, "Invalid r8 after pop");
    fail_unless(status.regs[14] == 0xdc, "Invalid r14 after pop");
    for(i = 0; i < 16; ++i)
        status.regs[i] = status_old.regs[i];
    
    stack[STACK_SIZE - 2] = 0x98;
    status.regs[13] = status_old.regs[13] - 2 * sizeof(addr_t);
    buf[0] = 0xa8; //pop r4, r14   
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 4 && i != 14)
            fail_unless(status.regs[i] == status_old.regs[i], "r[%d] corrupted", i);
    fail_unless(status.regs[14] == 0x98, "Invalid r14 after pop");
    status.regs[4] = status_old.regs[4];
    status.regs[14] = status_old.regs[14];

    // 10110001
    stack[STACK_SIZE - 2] = 0x33;
    stack[STACK_SIZE - 3] = 0x22;
    stack[STACK_SIZE - 4] = 0x11;
    stack[STACK_SIZE - 5] = 0x00;
    status.regs[13] = status_old.regs[13] - 4 * sizeof(addr_t);
    buf[0] = 0xb1; //pop only r0, r1, r2, r3;
    buf[1] = 0x0f;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 4; i < 16; ++i) 
        fail_unless(status.regs[i] == status_old.regs[i], "Registers corrupted");
    fail_unless(status.regs[ 3] == 0x33, "Invalid r3 after pop");
    fail_unless(status.regs[ 2] == 0x22, "Invalid r2 after pop");
    fail_unless(status.regs[ 1] == 0x11, "Invalid r1 after pop");
    fail_unless(status.regs[ 0] == 0x00, "Invalid r0 after pop");
    fail_unless(!status.r15_updated, "Flag for r15 update should not be set");
    for(i = 0; i < 4; ++i)
        status.regs[i] = status_old.regs[i];

    stack[STACK_SIZE - 2] = 0xab;
    stack[STACK_SIZE - 3] = 0xcd;
    status.regs[13] = status_old.regs[13] - 2 * sizeof(addr_t);
    buf[0] = 0xb1; //pop only r0, r3;
    buf[1] = 0x09;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    for(i = 0; i < 16; ++i) 
        if(i != 0 && i != 3)
            fail_unless(status.regs[i] == status_old.regs[i], "Registes corrupted");
    fail_unless(status.regs[ 3] == 0xab, "Invalid r3 after pop");
    fail_unless(status.regs[ 0] == 0xcd, "Invalid r0 after pop");
    fail_unless(!status.r15_updated, "Flag for r15 update should not be set");

}
END_TEST

//------------------------------------------------------------------------------

START_TEST (test_RegToSP) {
    status.regs[9] = 0xee;
    buf[0] = 0x99; // r9 to sp
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[13] == 0xee, "Invalid r13 after RegToSP");
    status.regs[9] = status_old.regs[9];

    status.regs[14] = 0xab;
    buf[0] = 0x9e; // r14 to sp
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[13] == 0xab, "Invalid r13 after RegToSP");
    status.regs[13] = status_old.regs[13];
    status.regs[14] = status_old.regs[14];

    int i = 0;
    for(i = 0; i < ARM_REGISTER_COUNT; ++i)
        fail_unless(status_old.regs[i] == status.regs[i], "Registers corrupted");
}
END_TEST

//------------------------------------------------------------------------------

START_TEST (test_Finish) {
    // without updated r15
    status.regs[15] = 0xaa;
    status.regs[14] = 0xef;
    buf[0] = 0xb0; 
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[15] == 0xef, "Invalid r15 after finish");
    status.regs[14] = status_old.regs[14];
    status.regs[15] = status_old.regs[15];

    // with updated r15
    stack[STACK_SIZE - 2] = 0x46;
    status.regs[13] = status_old.regs[13] - sizeof(addr_t);
    buf[0] = 0x88; //pop only r15;
    buf[1] = 0x00;
    buf[2] = 0xb0; 
    ret = ArmExidxDecode(&status, buf, 3); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    fail_unless(status.regs[15] == 0x46, "Invalid r15=[%x] after finish", 
                status.regs[15]);
    status.regs[15] = status_old.regs[15];

    int i = 0;
    for(i = 0; i < 16; ++i)
        fail_unless(status_old.regs[i] == status.regs[i],
                    "r[%d] corrupted: [%x]", i, status.regs[i]);
}
END_TEST

//------------------------------------------------------------------------------

START_TEST (test_OtherRegsPop) {
    // 10110011 sssscccc
    buf[0] = 0xb3;
    buf[1] = 0x6c;
    ret = ArmExidxDecode(&status, buf, 2); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    status.regs[13] -= 8 * 0xc + 4;
    fail_unless(status.regs[13] == status_old.regs[13],
                "Invalid r13=[%x] instead of [%x]", status.regs[13],
                status_old.regs[13]);

    // 10111nnn
    buf[0] = 0xbb;
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    status.regs[13] -= 8 * 3 + 4;
    fail_unless(status.regs[13] == status_old.regs[13], "Invalid r13");

    // 11000nnn (nnn != 6,7)
    buf[0] = 0xc5;
    ret = ArmExidxDecode(&status, buf, 1); 
    fail_unless(ret == 0, "ArmExidxDecode failed with [%s]", StrError(ret));
    status.regs[13] -= 8 * 5;
    fail_unless(status.regs[13] == status_old.regs[13], "Invalid r13");

    // 11000110 sssscccc

    // 11000111 0000iiii

    // 11001000 sssscccc

    // 11001001 sssscccc

    int i = 0;
    for(i = 0; i < 16; ++i)
        fail_unless(status_old.regs[i] == status.regs[i], "r[%d] corrupted", i);
}
END_TEST


//------------------------------------------------------------------------------

Suite *TestBacktracerArmExidx() {
    Suite *s = suite_create("BacktracerArmExidx");
    TCase *tc_all = tcase_create("Commands parcing");

    tcase_add_checked_fixture(tc_all, SetupChecked, TearDown);
    tcase_add_checked_fixture(tc_all, SetupUnchecked, TearDown);

    tcase_add_test(tc_all, test_DataPushPop);
    tcase_add_test(tc_all, test_RefuseReserved);
    tcase_add_test(tc_all, test_RegPop);
    tcase_add_test(tc_all, test_RegToSP);
    tcase_add_test(tc_all, test_Finish);
    tcase_add_test(tc_all, test_OtherRegsPop);

    suite_add_tcase(s, tc_all);
    return s;
}

